/**
 * 
 */
package com.google.code.rookery;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.code.rookery.converter.InstanceConverter;

/**
 * Class for making copies of java beans
 * @author Xerox
 */
public class Mimeograph {

  public static final String        REGEX_SETTER        = "^set.*";

  private static Map<String, InstanceConverter> converterMap = new HashMap<String, InstanceConverter>();

  /**
   * Loop through the getters of the from bean and try to match up to
   * setters on the to bean with the same property name. Both from and to
   * must conform to the Java bean interface for this to work.
   * @param from the bean being copied
   * @param to the bean being copied to
   * @throws InvocationTargetException 
   * @throws IllegalAccessException 
   * @throws IllegalArgumentException 
   * @throws InstantiationException 
   * @throws ClassNotFoundException 
   */
  public static final void copy(Object from, Object to)
        throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, InstantiationException{
    //find all of the setters in the to class
    for(Method setter:findSetters(to)){
      //see if there's a matching getter in the from class
      Method getter = findMatchingGetter(from, setter);
      if(getter!=null){
        Class<?> getType = getter.getReturnType();
        Class<?> setType = setter.getParameterTypes()[0];
        if(setType.isAssignableFrom(getType)){
          setter.invoke(to, getter.invoke(from));
        }
        else{
          //look for a converter
          String key = setType.getName() + "|" + getType.getName();
          InstanceConverter converter = converterMap.get(key);
          if(converter==null){
            //see if we've already added
            if(!converterMap.containsKey(key)){
              converter = InstanceConverter.findConverter(getType, setType);
              converterMap.put(key, converter);
            }
          }
          if(converter!=null){
            //use the converter
            Object val = converter.convert(getter.invoke(from));
            setter.invoke(to, val);
          }
        }
      }
    }
  }

  /**
   * For an object, find a getter with the property name as the setter
   * @param obj the object being searched
   * @param setter the setter being matched
   * @return the getter with the same name
   */
  public static final Method findMatchingGetter(Object obj, Method setter){
    Method getter = null;

    String propName = setter.getName().substring(3);
    final String getterRegex = "\\w\\w\\w?" + propName;

    for(Method method:obj.getClass().getMethods()){
      if(method.getName().matches(getterRegex)){
        //we matched on name, make sure it has one parameter and no return type
        if(method.getParameterTypes().length==0 && method.getReturnType()!=void.class){
          //must be the getter we're looking for
          getter = method;
          break;
        }
      }
    }

    return getter;
  }

  /**
   * Find the getters for a specified object
   * @param obj the object being searched
   * @return an array of all methods starting with get*
   */
  public static final Method[] findSetters(Object obj){
    List<Method> methods = new ArrayList<Method>();
    for(Method method:obj.getClass().getMethods()){
      if(method.getName().matches(REGEX_SETTER)){
        methods.add(method);
      }
    }

    Method[] ret = new Method[methods.size()];
    ret = methods.toArray(ret);
    return ret;
  }

  /**
   * Should never be constructed
   */
  private Mimeograph() {
  }
}
